﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using ProjectBase.Core;

using $prjsn$.Common.Validation;

namespace $safeprojectname$
{
    public class UniqueCollectionItemRule<TItem, TID, TValue> : RuleBase, IValidationRule<TItem>
        where TItem : class, IPersistentObjectWithTypedId<TID>
    {
        #region members

        private readonly Func<TItem, IEnumerable<TValue>> _compiledExpression;
        private readonly Func<TValue, bool> _predicate;
        private IEnumerable<string> _uniqueProperties;
        private Func<TValue, object> _property;

        #endregion members

        #region Constructor

        public UniqueCollectionItemRule(Func<TItem, IEnumerable<TValue>> expression
            , Func<TValue, bool> predicate
            , string propertyName
            , params string[] uniqueProperties)
        {
            _uniqueProperties = uniqueProperties;
            _compiledExpression = expression;
            _predicate = predicate;
            PropertyName = propertyName;
        }

        public UniqueCollectionItemRule(Expression<Func<TItem, IEnumerable<TValue>>> expression)
        {
            var body = expression.Body as MemberExpression;
            if (body.IsNull())
            {
                throw new NotSupportedException(
                    "Provided 'Expression<Func<TItem, IEnumerable<TValue>>>' must be of a 'ExpressionType.MemberAccess' (UniqueCollectionItemRule). ");
            }
            PropertyName = body.Member.Name;
            _compiledExpression = expression.Compile();
        }

        #endregion Constructor

        #region Validate

        public virtual ValidationResult Validate(TItem entity, out ValidationMessage message)
        {
            message = null;

            var list = CompiledExpression.Invoke(entity);
            if (list.IsEmpty())
            {
                return ValidationResult.IsValid;
            }

            if (Predicate.Is())
            {
                return ValidatePredicate(ref message, list);
            }

            if (Property.IsNull())
            {
                throw new ArgumentException("Rule was not correctly set, missing 'Predicate' or 'Property' delegate");
            }

            return ValidateProperty(ref message, list);
        }

        protected virtual ValidationResult ValidateProperty(ref ValidationMessage message, IEnumerable<TValue> list)
        {
            var count = list.Count();

            var equalityComparer = new PropertyComparer<TValue>(Property);
            var filteredResultCount = list.Distinct(equalityComparer).Count();

            var hasListUniqueItems = count.Equals(filteredResultCount);

            if (hasListUniqueItems)
            {
                return ValidationResult.IsValid;
            }

            message = new ValidationMessage("The collection contains multiple occurence of items with the same business settings. Uniqueness required over properties: [{0}]")
            {
                UniqueProperties.Localize<TItem>(),
            };
            return ValidationResult.Error;
        }

        protected virtual ValidationResult ValidatePredicate(ref ValidationMessage message, IEnumerable<TValue> list)
        {
            var filteredResults = list.Where(Predicate).ToList();

            var isItemUnique = filteredResults.IsEmpty()
                               || filteredResults.Count == 1;

            if (isItemUnique)
            {
                return ValidationResult.IsValid;
            }

            message = new ValidationMessage("The collection contains multiple occurence of items with the same business settings. Uniqueness required over properties: [{0}]")
            {
                UniqueProperties.Localize<TItem>(),
            };
            return ValidationResult.Error;
        }

        #endregion Validate

        #region Client

        public override IMDValidationRule GetMetaDataRule()
        {
            if (!IsMultiple.HasValue)
            {
                return null;
            }

            if (Predicate.Is())
            {
                var rule = new MDMultipleRule
                {
                    IsMultiple = IsMultiple.Value,
                    OverProperty = OverProperty,
                    PropertyValue = UniqueValue,
                };
                return rule;
            }

            return null;
        }

        public virtual void SetClientMetadataRule(bool isMultiple, string overProeprty, object uniqueValue)
        {
            IsMultiple = isMultiple;
            OverProperty = overProeprty;
            UniqueValue = uniqueValue;
        }

        #endregion Client

        #region fluent

        public virtual UniqueCollectionItemRule<TItem, TID, TValue> HasUniqueProperty(Expression<Func<TValue, object>> property)
        {
            var body = property.Body as MemberExpression;
            if (body.IsNull())
            {
                throw new NotSupportedException(
                    "Provided 'Expression<Func<TItem, IEnumerable<TValue>>>' must be of a 'ExpressionType.MemberAccess' (UniqueCollectionItemRule). ");
            }
            _uniqueProperties = new[] { body.Member.Name };
            _property = property.Compile();
            return this;
        }

        #endregion fluent

        #region properties

        protected virtual Func<TItem, IEnumerable<TValue>> CompiledExpression { get { return _compiledExpression; } }
        protected virtual Func<TValue, bool> Predicate { get { return _predicate; } }
        protected virtual Func<TValue, object> Property { get { return _property; } }

        protected virtual IEnumerable<string> UniqueProperties { get { return _uniqueProperties; } }

        protected virtual bool? IsMultiple { get; set; }
        protected virtual string OverProperty { get; set; }
        protected virtual object UniqueValue { get; set; }

        #endregion properties

        public class PropertyComparer<T> : IEqualityComparer<T>
        {
            private readonly Func<T, object> _property;

            public PropertyComparer(Func<T, object> property)
            {
                _property = property;
            }

            public bool Equals(T x, T y)
            {
                var _resultX = _property.Invoke(x);
                var _resultY = _property.Invoke(y);

                return _resultX.Equals(_resultY);
            }

            public int GetHashCode(T obj)
            {
                var _resultX = _property.Invoke(obj);
                return _resultX.ToString().ToLower().GetHashCode();
            }
        }
    }
}